package pers.richard.ormybatis.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.aop.support.AopUtils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtMethod;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

public class ReflectMethodByArgNameUtil {

	/**
	 * 根据paramMap的key值反射到同名方法参数上
	 * @param obj 对象
	 * @param methodName 方法名
	 * @param paramMap 传值key:方法参数名,参数值
	 */
	@SuppressWarnings("unchecked")
	public static Object invoke(Object obj, String methodName, Map<String, String> argsMap) throws ReflectiveOperationException {
		Class<?> targetClass = AopUtils.getTargetClass(obj);
		Method method = getMethodByName(targetClass, methodName);
		String[] argNames = getArgNames(method);
		if (ArrayUtil.isEmpty(argNames)) {
			return method.invoke(obj);
		}
		Class[] parameterTypeArray = method.getParameterTypes();
		Object[] args = new Object[argNames.length];
		for (int i = 0; i < argNames.length; i++) {
			Class argsClz = parameterTypeArray[i];
			String value = argsMap.get(argNames[i]);
			if (isPrimitive(argsClz)) {
				args[i] = Convert.convert(argsClz, value);
			} else if (Collection.class.isAssignableFrom(argsClz)) {
				Class genericType = Object.class;
				Type genericParameterType = method.getGenericParameterTypes()[i];
				if (genericParameterType instanceof ParameterizedType) {
					ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
					genericType = (Class)parameterizedType.getActualTypeArguments()[0];
				}
				args[i] = Convert.toCollection(argsClz, genericType, value);
			} else if (Map.class.isAssignableFrom(argsClz)) {
				Type genericParameterType = method.getGenericParameterTypes()[i];
				if (genericParameterType instanceof ParameterizedType) {
					ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
					Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
					Class keyClz = (Class)actualTypeArguments[0];
					Class valueClz =  (Class)actualTypeArguments[1];
					Map map = new LinkedHashMap();
					for (Map.Entry<String, String> entry : argsMap.entrySet()) {
						Object newKey = Convert.convert(keyClz, entry.getKey());
						Object newValue = Convert.convert(valueClz, entry.getValue());
						map.put(newKey, newValue);
					}
					args[i] = map;
				} else {
					args[i] = argsMap;
				}
			} else {
				args[i] = beanCopy(argsMap, argsClz);
			}
		}
		return method.invoke(obj, args);
	}

	/**
	 *	得到方法的参数变量名
	 */
	public static String[] getArgNames(Method method) {
		Class clazz = method.getDeclaringClass();
		ClassPool pool = ClassPool.getDefault();
		pool.insertClassPath(new ClassClassPath(clazz));
		try {
			CtMethod cm = pool.get(clazz.getName()).getDeclaredMethod(method.getName());
			MethodInfo methodInfo = cm.getMethodInfo();
			CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
			LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
			String[] paramNames = new String[cm.getParameterTypes().length];
			int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
			for (int i = 0; i < paramNames.length; i++) {
				paramNames[i] = attr.variableName(i + pos);
			}
			return paramNames;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 *	得到第一个指定方法名的method类
	 * @param clz
	 * @param methodName
	 * @return
	 */
	public static Method getMethodByName(Class clz, String methodName) {
		Method[] methodArray = clz.getMethods();
		for (Method method : methodArray) {
			if (method.getName().equalsIgnoreCase(methodName)) {
				return method;
			}
		}
		throw new RuntimeException("not found method name:[" + methodName + "]");
	}
	
	/**
	 *	如果是基本类型的则转换为基本类型数据
	 */
	public static boolean isPrimitive(Class clz) {
		return CharSequence.class.isAssignableFrom(clz) || Date.class.isAssignableFrom(clz) || Number.class.isAssignableFrom(clz) || ClassUtil.isBasicType(clz);
	}
	
	/**
	 * 支持不同命名方式的属性之间copy
	 */
	private static Object beanCopy(Map<String, String> sourceMap, Class destClz) {
        try {
        	Collection<String> keySet = sourceMap.keySet();
            List<String> destFieldNameList = Stream.of(ClassUtil.getDeclaredFields(destClz)).map(Field::getName).collect(Collectors.toList());
            Object result = destClz.newInstance();
            for (String key : keySet) {
                Object value = sourceMap.get(key);
                if (ObjectUtil.isEmpty(value)) {
                	continue;
                }
                //源和目标属性名一样set value
                if (ReflectUtil.hasField(destClz, key)) {
                    BeanUtil.setProperty(result, key, value);
                    continue;
                }
                //转换成下划线方式的属性名并set value
                String sourceFieldNameLowerUnderscore = CaseFormatUtil.toLowerUnderscore(key);
                if (ReflectUtil.hasField(destClz, sourceFieldNameLowerUnderscore)) {
                    BeanUtil.setProperty(result, sourceFieldNameLowerUnderscore, value);
                    continue;
                }
                //转换成驼峰式命名法并set value
                String sourceFieldNameLowerCamel = CaseFormatUtil.toLowerCamel(key);
                if (ReflectUtil.hasField(destClz, sourceFieldNameLowerCamel)) {
                    BeanUtil.setProperty(result, sourceFieldNameLowerCamel, value);
                    continue;
                }
                //忽略属性名大小并set value
                String destFieldNameIgnoreCase = destFieldNameList.stream().filter(fieldName -> fieldName.equalsIgnoreCase(key)).findFirst().orElse(null);
                if (destFieldNameIgnoreCase != null) {
                    BeanUtil.setProperty(result, destFieldNameIgnoreCase, value);
                }
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}